import {
  BulgeCurveParam,
  EBomCurveType,
  EGrooveGeometryType,
  Groove,
  Hole,
  IPathGroove,
  Number3,
  Path,
  Point,
  Point3,
  Profile,
  Rect,
} from '@/types';
import {
  bulgeCurveParam2IntermediaCurveParam,
  createLineCurveParam,
  getContinuousCurveParams,
  getCurveFromBulge,
  getDirectoryByPlankFace,
  getFillCurveParamByCircle,
  getOffsetCurveParams,
  getRoundedRectShape,
  intermediateCurveParam2BulgeCurveParam,
  number3ToPoint,
  point3point2,
  reverseCurveParams,
} from '@/utils';
import { clone, cloneDeep, flow, isEqual, map, merge } from 'lodash-es';
import {
  Box2,
  Euler,
  Line3,
  LineCurve3,
  Matrix4,
  Quaternion,
  Shape,
  Vector2,
  Vector3,
} from 'three';
import { config } from '../config';

export function getPointsFromRect(rect: Rect) {
  const { width, height } = rect;
  let points: Point[];
  const offset = (x: number, y: number): Point => [
    -width / 2 + x,
    -height / 2 + y,
  ];
  points = [
    [0, 0],
    [0, height],
    [width, height],
    [width, 0],
    // [0, 0],
  ];
  points = points.map(([x, y]) => offset(x, y));
  return points;
}
export function getShapeFromPoints(points: Point[]) {
  const shape = new Shape();
  const [startPoint, ...otherPoints] = points;
  {
    const [x, y] = startPoint;
    shape.moveTo(x, y);
  }
  otherPoints.map(([x, y]) => {
    shape.lineTo(x, y);
  });
  return shape;
}
export function getShapeFromPath({
  path,
  width,
}: {
  path: Path;
  width: number;
}): Shape {
  const pathCurveParams = getCurveParamsFromPath(path);

  const offsetCurveParamsGroup: {
    outer: BulgeCurveParam[];
    inner: BulgeCurveParam[];
  } = { outer: [], inner: [] };
  map(pathCurveParams, (curveParam) => {
    const { outerCurveParam, innerCurveParam } = getOffsetCurveParams(
      curveParam,
      width,
    );
    offsetCurveParamsGroup.outer.push(outerCurveParam);
    offsetCurveParamsGroup.inner.push(innerCurveParam);
  });

  const [outerCurveParams, innerCurveParams] = [
    {
      offsetCurveParams: offsetCurveParamsGroup.outer,
      isCCW: false,
    },
    {
      offsetCurveParams: offsetCurveParamsGroup.inner,
      isCCW: true,
    },
  ].map(({ offsetCurveParams, isCCW }) =>
    flow(
      ({
        offsetCurveParams,
        isCCW,
      }: {
        offsetCurveParams: BulgeCurveParam[];
        isCCW: boolean;
      }) => ({
        intermediateOffsetCurveParams: offsetCurveParams.map(
          bulgeCurveParam2IntermediaCurveParam,
        ),
        isCCW,
      }),
      ({ intermediateOffsetCurveParams, isCCW }) =>
        getContinuousCurveParams(
          intermediateOffsetCurveParams,
          width,
          (pairCurveParam, index) => {
            const center = pathCurveParams[index].endPoint;
            const radius = width;
            const circle = { center, radius };
            return getFillCurveParamByCircle(pairCurveParam, circle, isCCW);
          },
        ),
    )({ offsetCurveParams, isCCW }),
  );

  const linkCurveParamsGroup = {
    start: createLineCurveParam({
      startPoint: outerCurveParams.at(-1)!.endPoint,
      endPoint: innerCurveParams.at(-1)!.endPoint,
    }),
    end: createLineCurveParam({
      startPoint: innerCurveParams[0].startPoint,
      endPoint: outerCurveParams[0].startPoint,
    }),
  };
  const composeCurveParams = [
    ...outerCurveParams,
    linkCurveParamsGroup.start,
    ...reverseCurveParams(innerCurveParams),
    linkCurveParamsGroup.end,
  ];

  const curves = composeCurveParams.map(
    flow(intermediateCurveParam2BulgeCurveParam, getCurveFromBulge),
  );
  const shape = new Shape();
  curves.forEach(shape.add.bind(shape));

  // console.log({
  //   outerCurveParams,
  //   innerCurveParams,
  //   composeCurveParams,
  //   composeCurveParamsPointsStr: getPointsStrFromStartEnd(composeCurveParams),
  //   shape,
  // });
  return shape;
}
export function getShapeFromProfile(profile: Profile) {
  const curveParams = getCurveParamsFromProfile(profile);
  return getShapeFromCurveParam(curveParams);
}
function getShapeFromCurveParam(curveParams: BulgeCurveParam[]) {
  const shape = new Shape();
  const curves = curveParams.map(getCurveFromBulge);
  curves.forEach(shape.add.bind(shape));
  return shape;
}
export const getRectShape = flow(getPointsFromRect, getShapeFromPoints);
export function getClosedProfile(profile: Profile) {
  const { points, curves } = profile;
  const firstPoint = points.slice(0, 2);
  const lastPoint = points.slice(-2);
  if (isEqual(firstPoint, lastPoint)) return profile;
  const newPoints = clone(points).concat(firstPoint);
  const newCurves = clone(curves).concat({
    type: EBomCurveType.LineSeg,
    bulge: 0,
  });
  const newProfile: Profile = merge(cloneDeep(profile), {
    points: newPoints,
    curves: newCurves,
  });
  return newProfile;
}
export function getPlankShape({
  profile,
  inners,
  width,
  height,
}: {
  profile: Profile;
  inners?: Profile[];
  width: number;
  height: number;
}) {
  let shape: Shape;
  if (!profile) {
    shape = getRectShape({ width, height });
  } else {
    shape = getShapeFromProfile(profile);
  }
  if (inners) {
    shape.holes = inners.map((profile) => getShapeFromProfile(profile));
  }
  return shape;
}

export function getHoleShape(hole: Hole) {
  const { diameter } = hole;

  const shape = new Shape();
  shape.absarc(0, 0, diameter / 2, 0, 2 * Math.PI, true);

  return shape;
}

export function getGrooveShapeAndMatrix(
  groove: Groove,
  plank: { width: number; height: number; thickness: number },
) {
  const { width, depth, geometryType, plankFaceId } = groove;

  let shape = new Shape();
  let matrix = new Matrix4();
  // 顶面
  if (Number(plankFaceId) === 0) {
    matrix.makeTranslation(0, 0, plank.thickness / 2);
  }
  if (Number(plankFaceId) === 5) {
    matrix.makeTranslation(0, 0, -(plank.thickness / 2 - depth));
  }

  switch (geometryType) {
    case EGrooveGeometryType.ROUND_CORNER: {
      const { radius } = groove;
      const w = width!;
      const h = depth;
      const r = radius!;
      shape = getRoundedRectShape(new Shape(), w / 2, h / 2, w, h, r);
      break;
    }

    case EGrooveGeometryType.SPECIAL_SHAPED: {
      const { profile } = groove;
      shape = getShapeFromProfile(profile);
      break;
    }

    case EGrooveGeometryType.PATH: {
      const pathGroove = groove as IPathGroove;
      shape = getShapeFromPath(pathGroove);
      break;
    }

    case EGrooveGeometryType.SQUARE:
    default: {
      // 从当前顶点转到x,y平面
      matrix = getGrooveMatrix(groove.plankFaceId, [
        number3ToPoint(groove.start),
        number3ToPoint(groove.end),
      ]);
      // 以槽的起点和和终点所在的面作为挤压几何的终点 = 取反矩阵的旋转方向
      const direction = getDirectoryByPlankFace(Number(plankFaceId), Vector3);
      matrix.premultiply(
        new Matrix4().makeRotationFromQuaternion(
          new Quaternion().setFromUnitVectors(
            direction,
            direction.clone().negate(),
          ),
        ),
      );
      // 将点转换成x, y水平面上的点
      const points = getSquareGroovePoints(groove, matrix);
      matrix.invert();
      shape = getShapeFromPoints(points.map(point3point2));
    }
  }

  const translation = new Vector3();
  const quaternion = new Quaternion();
  matrix.decompose(translation, quaternion, new Vector3());
  const euler = new Euler().setFromQuaternion(quaternion);

  return { shape, matrix, translation, euler };
}
export function getEdgeColorConfig(edgeBanding: number[]) {
  return edgeBanding.map((thickness) => {
    const target = config.edgeBanding.find((item) => {
      return item.thickness.includes(thickness);
    });
    const color = target?.color ?? '';
    return { thickness, color };
  });
}

type BoundBoxVertexPoints = [Point, Point, Point, Point];
/** 从板件的轮廓和宽高中获取边框盒的四个顶点 [a,b,c,d] */
export function getBoundBoxVertexPointsFromPlank({
  width,
  height,
  profile,
}: {
  width: number;
  height: number;
  profile: Profile;
}) {
  if (/* false && */ profile) {
    return flow(
      getShapeFromProfile,
      (shape: Shape) =>
        shape.getPoints().map((point) => {
          // point.multiplyScalar(2)
          return point.toArray();
        }),
      getBoundBoxVertexPointsFromPoints,
    )(profile);
  } else {
    return getPointsFromRect({ width, height });
  }
}
/** 从轮廓点中获取边框盒的四个顶点 [a,b,c,d] */
function getBoundBoxVertexPointsFromPoints(
  points: Point[],
): BoundBoxVertexPoints {
  const box2 = new Box2();
  points.forEach((point) => {
    box2.expandByPoint(new Vector2().fromArray(point));
  });
  const { min, max } = box2;
  return [
    [min.x, min.y],
    [min.x, max.y],
    [max.x, max.y],
    [max.x, min.y],
  ];
}

/** 从点中获取两点间的线段组成的数组 */
export function getLinesFromPoints(points: Point[]) {
  const lines: [Point, Point][] = [];
  const pointNum = points.length;
  for (let index = 0; index < points.length; index++) {
    const point = points[index];
    let nextIndex = index + 1;
    if (nextIndex >= pointNum) {
      nextIndex = 0;
    }
    const nextPoint = points[nextIndex];
    lines.push([point, nextPoint]);
  }
  return lines;
}
export function getLinesFromReact(rect: Rect) {
  const points = getPointsFromRect(rect);

  const lines: [Point, Point][] = [];
  const pointNum = points.length;
  for (let index = 0; index < points.length; index++) {
    const point = points[index];
    let nextIndex = index + 1;
    if (nextIndex >= pointNum) {
      nextIndex = 0;
    }
    const nextPoint = points[nextIndex];
    lines.push([point, nextPoint]);
  }
  return lines;
}

export function getCurveParamsFromProfile(profile: Profile) {
  const { points, curves } = getClosedProfile(profile);
  return curves.map((curve, i) => {
    const { bulge = 0 } = curve;
    const startPoint = { x: points[2 * i], y: points[2 * i + 1] };
    const endPoint = {
      x: points[2 * ((i + 1) % curves.length)],
      y: points[2 * ((i + 1) % curves.length) + 1],
    };
    return { startPoint, endPoint, bulge };
  });
}
export function getCurveParamsFromPath(path: Path): BulgeCurveParam[] {
  const { points, curves } = path;
  const curveParams = curves.map((curve, i) => {
    const { bulge = 0 } = curve;
    const startPoint = { x: points[2 * i], y: points[2 * i + 1] };
    const endPoint = {
      x: points[2 * (i + 1)],
      y: points[2 * (i + 1) + 1],
    };
    return { startPoint, endPoint, bulge };
  });
  return curveParams;
}

export function getGrooveMatrix(
  plankFaceId: string | number,
  points: Point3[],
): Matrix4 {
  const direction = getDirectoryByPlankFace(Number(plankFaceId), Vector3);

  const centerPoint = points
    .map((point) => {
      return new Vector3().fromArray(point);
    })
    .reduce((pre, cur) => {
      return pre.add(cur);
    })
    .multiplyScalar(1 / points.length);

  const matrix = new Matrix4()
    .makeTranslation(-centerPoint.x, -centerPoint.y, -centerPoint.z)
    .premultiply(
      new Matrix4().makeRotationFromQuaternion(
        new Quaternion().setFromUnitVectors(direction, new Vector3(0, 0, 1)),
      ),
    );

  return matrix;
}
export function getSquareGroovePoints(
  { start, end, width, plankFaceId }: Groove,
  matrix: Matrix4,
): Point3[] {
  const startVec = new Vector3(start.x, start.y, start.z);
  const endVec = new Vector3(end.x, end.y, end.z);
  const line = new Line3(startVec, endVec);
  const lineDirection = line.delta(new Vector3());
  const planNormal = getDirectoryByPlankFace(Number(plankFaceId), Vector3);

  const pointOffsetDiretion = lineDirection
    .cross(planNormal)
    .normalize()
    .setLength(width! / 2);

  return [
    startVec.clone().add(pointOffsetDiretion),
    endVec.clone().add(pointOffsetDiretion),
    endVec.clone().sub(pointOffsetDiretion),
    startVec.clone().sub(pointOffsetDiretion),
  ].map((vec) => vec.applyMatrix4(matrix).toArray());
}

export function getExtrudePathByStartAndEnd(points: {
  start: Number3;
  end: Number3;
}) {
  const { start, end } = points;
  const p1 = new Vector3(start.x, start.y, start.z); //顶点1坐标
  const p2 = new Vector3(end.x, end.y, end.z); //顶点2坐标

  // 三维直线LineCurve3
  const LineCurve = new LineCurve3(p1, p2);

  return LineCurve;
}
